# ======================================================================
#
# Copyright (C) ADN
# ADN::LIBRARY::SSH::ClientSSH
# Description:
#  - Client SSH
#  - SSH client
#
# ======================================================================

package ADN::LIBRARY::SSH::ClientSSH;

use strict;
use IPC::Run qw( start pump finish timeout );
use Time::HiRes qw(sleep);
use ADN::CONF::Read;
use ADN::LOG::Log;
use ADN::LANG::Lang;

# #######################
# CREATE OBJECT
# #######################
sub new
{
	# Construction de l'objet $self
	my $self = shift;
	my $class = ref($self) || $self;
	return $self if ref $self;
	$self = bless {} => $class;
	$self->{line} = '';
  $self->{log} = ADN::LOG::Log->new();
  %{$self->{lang}} = ADN::LANG::Lang->new()->ssh();
	$self->_init();
	my $return = $self->connect($_[0], $_[1], $_[2], $_[3]);
	return $self if ($return == 1);
	return undef;
}

# #######################
# INITIALISATION
# #######################
sub _init
{
  	my $self = shift;
  	my $conf = ADN::CONF::Read->new();
  	if ( defined $conf ) {
    		if ( $conf =~ m/^[-]1$/smx ) {
      			return -1;
    		}
    		else {
      			my %ssh = $conf->SSH();
      			$self->{path} 										= $ssh{path};
      			$self->{timeout} 									= $ssh{timeout};
      			$self->{cache_key} 								= $ssh{cache_key};
      			$self->{compression} 							= $ssh{compression};
      			$self->{cache_key} 								= $ssh{cache_key};
      			$self->{update_cached_key} 				= $ssh{update_cached_key};
      			$self->{opened}             			= 0;
						$self->{continue_with_connection} = $ssh{continue_with_connection};
						$self->{ors}				        		  = "\n";
						if ( $^O eq 'MSWin32' ) {
							$self->{path2} 									= '/windows/';
						}
						elsif ( $^O eq 'linux' || $^O eq 'darwin' ) {
       				$self->{path2} 									= '/linux/';
						}
      			$self->{path_inc} 								= $conf->_send_path();
      			$self->{path}     								= $self->{path_inc}.$self->{path}.$self->{path2};
      			undef %ssh;
		    }
    		undef $conf;
  	}
  	return;
}

# #######################
# CONNECT TO HOST
# #######################
sub connect
{
	my $self       = shift;
	my $ip_address = shift;
	my $username   = shift;
	my $password   = shift;
	$self->{port}  = shift;

	if ( !defined($self->{port}) || $self->{port} <= 0 )
	{
		$self->{port} = 22;
	}
	if ( !$self->{opened} )
	{
		if ( $username eq '' )
		{
      $self->{log}->Log( $self->{lang}{mess1}, 2, 5 );
      return 0;
		}

		my @cmd = ();
		push @cmd, $self->{path}.'/plink';
		push @cmd, '-ssh', '-v', '-a', '-x';    # Verbose output, disable agent forwarding, and disable X11 forwarding
		push @cmd, '-' . $self->{version} if $self->{version} =~ /^[12]$/smx;
		push @cmd, ( '-l',  $username ) if $username;
		push @cmd, ( '-pw', $password ) if $password;
		push @cmd, '-C' if $self->{compression};
		push @cmd, ( '-P', $self->{port} );
		push @cmd, $ip_address;

		eval { $self->{ssh} = start \@cmd, \$self->{in}, \$self->{out}, \$self->{err}, ( $self->{timer} = timeout( $self->{timeout} ) ); };
    if ($@)
    {
			$self->{log}->Log( $self->{lang}{mess2}.$ip_address, 2, 5 );
			$self->{log}->Log( $self->{lang}{mess3}.$self->{err}, 2, 5 );
			return 0;
    }

		my ( $have_version, $started, $store_key_prompt, $update_cached_key_prompt, $continue_with_connection_prompt ) = ( 0, 0, 0, 0, 0 );
		my $errorBuffer = '';
		while ( !$have_version || !$started )
		{
			if (length $self->{err} <= 0)
			{
				eval { $self->{ssh}->pump until (length $self->{err} > 0); };
				if ($@)
				{
					print $self->{out};
					return 0;
				}
			}
			$errorBuffer .= $self->{err};
      $self->{line} .= $self->{err};
			$self->{err} = '';

			if ( !$have_version && $errorBuffer =~ /Using SSH protocol version (\d+)/smx )
			{
				$have_version = 1;
				$self->{version} = $1;
				$self->{log}->Log( $self->{lang}{mess4}.$self->{version}, 2, 5 );
			}
			if ( !$store_key_prompt && $errorBuffer =~ /Store key in cache/sm )
			{
				$store_key_prompt = 1;
				$self->send( $self->{cache_key} ? 'y' : 'n' );
			}
			if ( !$update_cached_key_prompt && $errorBuffer =~ /Update cached key/sm )
			{
				$update_cached_key_prompt = 1;
				$self->send( $self->{update_cached_key} ? 'y' : 'n' );
			}
			if ( !$continue_with_connection_prompt && $errorBuffer =~ /Continue with connection/sm )
			{
				$continue_with_connection_prompt = 1;
				$self->send( $self->{continue_with_connection} ? 'y' : 'n' );
			}
			if ( !$started && $errorBuffer =~ /Started session|Started a shell/ismx )
			{
				$started = 1;
				$self->{err} = '';
				$self->{log}->Log( $self->{lang}{mess5}.$ip_address, 2, 5 );
			}
			elsif ( !$started && $errorBuffer =~ /Connection refused/sm )
			{
				$self->{opened} = 0;
				$self->{log}->Log( $self->{lang}{mess6}.$ip_address, 2, 5 );
				last;
			}
			elsif ( !$started && $errorBuffer =~ /'plink' is not recognized|plink:\s+command not found/smx )
			{
				$self->{opened} = 0;
				$self->{log}->Log( $self->{lang}{mess7}.$ip_address, 2, 5 );
				last;
			}
			elsif ( !$started && $errorBuffer =~ /Connection timed out/sm )
			{
				$self->{opened} = 0;
				$self->{log}->Log( $self->{lang}{mess8}.$ip_address, 2, 5 );
				last;
			}
			elsif ( !$started && $errorBuffer =~ /Unable to authenticate|password:\s*$/smx )
			{
				$self->{opened} = 0;
				$self->{log}->Log( $self->{lang}{mess9}, 2, 5 );
				last;
			}
			elsif ( !$started && $errorBuffer =~ /Fatal:|Fatal error:/ismx )
			{
				$self->{opened} = 0;
				$self->{log}->Log( $self->{lang}{mess7}.$ip_address, 2, 5 );
				last;
			}
			elsif ( !$started && $errorBuffer =~ /Host does not exist/sm )
			{
				$self->{opened} = 0;
				$self->{log}->Log( $self->{lang}{mess7}.$ip_address, 2, 5 );
				last;
			}
			elsif ( !$started && $errorBuffer =~ /Disconnected/sm )
			{
				$self->{opened} = 0;
				$self->{log}->Log( $self->{lang}{mess7}.$ip_address, 2, 5 );
				last;
			}
			elsif ( !$started && $errorBuffer =~ /unknown option/sm )
			{
				$self->{opened} = 0;
				$self->{log}->Log( $self->{lang}{mess7}.$ip_address, 2, 5 );
				last;
			}
		}
		$self->{opened} = 1;
		return $started;
	}
  return;
}

# #######################
# SEND
# #######################
sub send
{
	my $self = shift;

  if (defined $self->{ssh})
  {
		$self->_empty_buffers;
		$self->{log}->Log( $self->{lang}{mess10}."\"".$_[0]."\"", 2, 5 );
		$self->{in} .= $_[0] . $self->{ors};
		eval {
				while ( $self->{in} )
				{
					 $self->{ssh}->pump;
				}
			};
	  return $self->{out};
	}
  return;
}

# #######################
# SEND BYTES
# #######################
sub send_as_bytes
{
	my $self  = shift;

	if ($_[0] !~ /^$/sxm)
	{
		my $length     = length $_[0];
		my $pack_arg   = 'H' . $length;
		my $byte_input = pack $pack_arg, $_[0];

		return ($self->put($byte_input));
	}
	return -1;
}

# #######################
# COMMAND PUT
# #######################
sub put
{
	my $self  = shift;

	$self->_empty_buffers();
	$self->{log}->Log( $self->{lang}{mess10}.'"'.$_[0].'"', 2, 5 );
	$self->{in} = $_[0];
  while ( $self->{in} )
  {
	  $self->{ssh}->pump_nb;
  }
	return $self->{out};
}

# #######################
# GET LAST LINE
# #######################
sub get_lastline
{
  my $self = shift;
  my ($get);

  if (defined $self->{ssh})
  {
    $get = $self->get_response($_[0]);;
    if ($get) {
      $get =~ s/^.*[\n|\r]+//sxm;
      $get =~ s/^.*[\n|\r]+(\w[^\n|^\r]*)$/$1/sxm;
      $get =~ s/^\s*|\s*$//gsxm;
      $self->{log}->Log( $self->{lang}{mess11}.'"'.$get.'"', 2, 5 );
			return $get;
    }
    return -1;
  }
  return;
}

# #######################
# BOUCLE SEND + GET
# #######################
sub boucle_get
{
  my $self = shift;
  my ($line);

  if (defined $self->{ssh})
  {
     $self->_empty_buffers;
     foreach $line (@{$_[0]}) {
       $self->send($line);
     }
     return ($self->get_response());
  }
  return;
}

# #######################
# FUNCTION WAIT
# #######################
sub wait_for
{
	my $self  = shift;
	my $etat = 0;

	my $timeout = defined $_[1] ? shift:$self->{timeout};
	$self->{timer}->start($timeout);
  $self->{log}->Log( $self->{lang}{mess12}.'"'.$_[0].'"', 2, 5 );
	eval { $self->{ssh}->pump until $self->{out} =~ /$_[0]/smx; };
  if ($@)
	{
		$etat = -1;
	}
  $self->{line} .= $self->{out};
	my $response = $self->{out};
	if ( defined $self->{wait_for_chatter} )
	{
		my $additionalChatter = $self->get_response($_[2]);
		$response .= $additionalChatter;
	}
	return ($response, $etat);
}

# #######################
# RECUP RESPONSE
# #######################
sub get_response
{
	my $self = shift;
	my $timeout = defined $_[0] ? shift: 5;
	my $response = '';
	my $chatter  = '';

	do
	{
		$chatter = $self->_get_chatter($timeout);
		if ( defined $chatter )
		{
			$response .= $chatter;
			$self->{line} .= $response;
		}
	} while ( defined $chatter );
	return $response;
}

# #######################
# RECUP RESPONSE  2
# #######################
sub _get_chatter
{
	my $self = shift;
	my $timeout = defined $_[0] ? shift: 5;
	my $chatter = '';

	$self->{timer}->start($timeout);
	eval {
		while (1)
		{
			$self->{ssh}->pump;
		}
	};
	$chatter = $self->{out};
	$self->_empty_buffers;
	return ( length $chatter > 0 ) ? $chatter : undef;
}

# #######################
# CLEAN BUFFERS
# #######################
sub _empty_buffers
{
	my $self = shift;
	$self->{out} = '';
	$self->{err} = '';
	return;
}

# #######################
# SEND CHAR
# #######################
sub send_char
{
  my $self = shift;
  my $nbr = $_[3];
  my ($response, $response2, $etat);

  if (defined($self->{ssh}))
  {
    while ($nbr != 0) {
      ($response, $etat) = $self->wait_for($_[0]);
      if (defined $response && ($etat !~ m/^[-]1$/smx)) {
        $response2 .= $response;
        $self->send_as_bytes($_[1]);
      }
      else {
        $response2 .= $response;
        last;
      }
      $nbr--;
    }
    return $response2;
  }
  return (-1);
}

# #######################
# FUNCTION PROMPT
# #######################
sub calcul_prompt
{
  my $self = shift;
  my $result = '';
  my $etat;

  if (!defined $_[1]) {
    ($result, $etat) = $self->wait_for($_[0]);
  }
  else {
    $result = $_[1];
  }
  $result =~ s/^.*[\n|\r]+(\w[^\n|^\r]*)$/$1/smx;
  $result = quotemeta $result;
  $result =~ s/^(\S+)(\S{2})$/$1\\S*$2/smx;
  $result = $result.'\s*';
  $self->{log}->Log( $self->{lang}{mess13}."\"".$result."\"", 1, 5 );
  return $result;
}

# #######################
# FUNCTION DISCONNECT
# #######################
sub disconnect
{
    my $self = shift;

    if ( $self->{ssh} )
    {
        eval {
            $self->{timer}->start(1);
            $self->{handle}->pump until $self->{out} =~ /.*/smx;
        };
        eval {
            $self->{ssh}->finish;
            $self->{ssh}->kill_kill;
        };
        if ($@)
        {
            $self->{ssh}->kill_kill( grace => 1 );
        }
        $self->{log}->Log( $self->{lang}{mess15}."\n".$self->{line}, 1, 5 );
        $self->{log}->Log( $self->{lang}{mess14}, 1, 5 );
        $self->{opened} = 0;
    }
    return;
}

# #######################
# DESTROY OBJECT
# #######################
sub DESTROY {
    my $self = shift;
    return;
}
1;
__END__
